import { Options, PipelineOperator } from "../../core";
import { Iterator } from "../../lazy";
import { AnyObject } from "../../types";
import { assert, cloneDeep, isArray, isString } from "../../util";
import { AsyncIterator } from "../../async/lazy";
/**
 * Takes the documents returned by the aggregation pipeline and writes them to a specified collection.
 *
 * Unlike in MongoDB, this operator can appear in any position in the pipeline and is
 * useful for collecting intermediate results of an aggregation operation.
 *
 * Note: Object are deep cloned for output regardless of the {@link ProcessingMode}.
 *
 * See {@link https://www.mongodb.com/docs/manual/reference/operator/aggregation/out/ usage}.
 *
 * @param collection
 * @param expr
 * @param options
 * @returns
 */
export const $out: PipelineOperator = (
  collection: Iterator | AsyncIterator,
  expr: string | AnyObject[],
  options: Options
): Iterator | Promise<AsyncIterator> => {
  const outputColl: AnyObject[] = isString(expr)
    ? options?.collectionResolver(expr)
    : expr;
  assert(isArray(outputColl), `expression must resolve to an array`);
  if (options.isAsync) {
    return Promise.resolve(collection as AsyncIterator).then((collection: AsyncIterator) => {
      return collection.map((o: AnyObject) => {
        outputColl.push(cloneDeep(o) as AnyObject);
        return o; // passthrough
      });
    })
  }
  return (collection as Iterator).map((o: AnyObject) => {
    outputColl.push(cloneDeep(o) as AnyObject);
    return o; // passthrough
  });
};
